const wv = document.getElementById('myWebview')

// webview中页面加载完毕
wv.addEventListener('dom-ready', () => {
    // wv.openDevTools();
    // 向webview注入js脚本
    wv.executeJavaScript(`
    // 元素选择相关功能
class ElementSelector {
    // 鼠标所在的元素
    #overElement = null
    // 鼠标所在元素的初始色（鼠标未进入前的颜色）
    #overElementOriginBackgroundColor = null
    // 已经选中的元素，key:element,value=[原始背景色，原始边框样式]
    #selectedElements = new Map()
    // 预选中的元素
    #preSelectElements = new Map()
    // 记录选中的元素的xpath
    #selectedXpathMatchUnique = []
    #selectedXpathMatchSimilar = null
    // 递归获取xpath时，记录沿途节点的tagName
    #xpathTemplate = []
    // 是否允许运行预选算法
    #allowPreSelect = true
    // 测试xpath时选中的元素
    #testXpathSelectElements = new Map()
    // 选择元素时的场景。0-正常选择场景，1-修改节点属性，用鼠标获取xpath的场景
    #selectScene = 0

    constructor() {
    }

    #isUnique(xpath) {
        const result = document.evaluate(xpath, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null)
        return result.snapshotLength === 1 // 唯一性验证
    }

    #getXpath(element) {
        // 反向记录xpath路径上的元素类型
        this.#xpathTemplate.push(element.tagName)
        const tagName = element.tagName.toLowerCase()
        // 递归到根节点，终止
        if (tagName === 'body') {
            return "//body"
        }
        if (element.id) {
            return \`//\${tagName}[@id='\${element.id}']\`
        }
        if (element.className) {
            const xpath = \`//\${tagName}[@class='\${element.className}']\`
            if (this.#isUnique(xpath)) {
                return xpath
            }
        }
        if (element.name) {
            const xpath = \`//\${tagName}[@name="\${element.name}"]\`
            if (this.#isUnique(xpath)) {
                return xpath
            }
        }
        // 向上递归
        let parentXpath = this.#getXpath(element.parentElement)
        // 获取当前元素在同类兄弟节点中的索引
        let index = 1
        let previousSibling = element.previousElementSibling
        while (previousSibling) {
            if (previousSibling.tagName.toLowerCase() === tagName) {
                index++
            }
            previousSibling = previousSibling.previousElementSibling
        }
        return \`\${parentXpath}/\${tagName}[\${index}]\`
    }

    #getXpathMatchUnique(element) {
        const xpath = this.#getXpath(element)
        this.#selectedXpathMatchUnique.push(xpath)
        return xpath
    }

    // 将返回一个xpath，能匹配到用户选中的元素以及同类元素
    #getXpathMatchSimilar(element) {
        // 获取用户选中的那一个元素的xpath，并获取到每一层的内容
        // ej. xpath: //div[@class='test']/ul[2]/li[1]/span[2]
        //     xpathSplit: ["div[@class='test']","ul[2]","li[1]","span[2]"]
        //     xpathTemplate: ['DIV','UL','LI','SPAN']
        this.#getXpathMatchUnique(element)
        this.#xpathTemplate.reverse()
        let xpathSplit = this.#selectedXpathMatchUnique[0].split('/')
        if (xpathSplit[1] === '') {
            xpathSplit = xpathSplit.slice(2)
        } else {
            xpathSplit = xpathSplit.slice(1)
        }

        // 基于xpathSplit和xpathTemplate组装出能匹配多个同类元素的xpath
        // 规则为：取通过特殊属性(id,class,name)定位的前缀，这部分保持不变，之后的部分去除索引部分
        let xpathMathMore = ['/']
        for (let i = 0; i < xpathSplit.length; i++) {
            xpathMathMore.push('/')
            if (xpathSplit[i].includes('@')) {
                xpathMathMore.push(xpathSplit[i])
            } else {
                xpathMathMore.push(this.#xpathTemplate[i].toLowerCase())
            }
        }

        return this.#selectedXpathMatchSimilar = xpathMathMore.join('')
    }

    mouseOver(event) {
        const target = event.target
        // 恢复之前高亮元素的背景色，这种情况是鼠标进入一个元素的子元素时，恢复该元素的父元素的背景色
        if (this.#overElement) {
            this.#overElement = this.#overElementOriginBackgroundColor
        }
        // 记录当前元素及其背景色
        this.#overElement = target;
        this.#overElementOriginBackgroundColor = target.style.backgroundColor
        // 修改当前目标元素的背景色
        target.style.backgroundColor = 'rgba(135, 206, 250, 0.5)';
    }

    mouseOut(event) {
        const target = event.target
        if (this.#overElement !== target) {
            return
        }
        target.style.backgroundColor = this.#overElementOriginBackgroundColor
        this.#overElement = null
        this.#overElementOriginBackgroundColor = null
    }

    mouseSelect(event) {
        event.preventDefault();
        // 如果此时有测试xpath的元素选中，先清除
        if (this.#testXpathSelectElements.size !== 0) {
            this.#clearXpathTestSelect()
        }

        let element = event.target
        if (this.#selectScene === 1) {
            // 当处于修改节点属性的场景时，鼠标点击的元素就是用户想要获取xpath的元素
            this.#getXpath(element)
            window.embeddedPageEvent.returnSelectXpath(this.#getXpath(element))
            return
        }

        // 再次选中已选中元素，不会发生任何事
        if (this.#selectedElements.has(element)) {
            return
        }
        // 在被预选的元素中用户进行了选中，我们认为用户不想使用预选。当进行下轮选择时(此轮选择被取消或采用)，预选才将被再次开启
        if (this.#preSelectElements.has(element)) {
            this.#selectedXpathMatchSimilar = null
            this.#selectedXpathMatchUnique.push(this.#getXpath(element))
            this.#select(element)
            this.#selectedElements.set(element, this.#preSelectElements.get(element))
            // 方便鼠标移出该元素时，恢复其为被选中的样式
            this.#overElementOriginBackgroundColor = 'rgba(13,142,4,0.3)'

            this.#preSelectElements.delete(element)
            // 其余预选元素被还原
            this.#preSelectElements.forEach((v, k, m) => this.#cancelSelect(k, v))
            this.#preSelectElements.clear()
            // 关闭预选
            this.#allowPreSelect = false
            return
        }

        // 用户选择了一个新的元素，这要分三种情况
        // 1.预选算法选中了0个元素，此时不会在运行预选算法
        // 2.用户应用了上一次的预选结果，此时不会运行预选算法。“选择全部”的回调会修改allowPreSelect
        // 3.用户放弃了上一次的预选结果，此时会重新运行预选算法。相当于进行了新一轮的选择
        if (this.#allowPreSelect && (this.#preSelectElements.size > 1 || this.#selectedXpathMatchUnique.length === 0)) {
            this.reset()
            this.#selectedElements.set(element, [this.#overElementOriginBackgroundColor, element.style.border])
            this.#select(element)
            // 获取到一个通用xpath，匹配到所有相似元素
            const xp = this.#getXpathMatchSimilar(element)
            const allMatchElements = document.evaluate(xp, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null)
            // 遍历所有相似元素，将其设置为预选中的样式
            for (let i = 0; i < allMatchElements.snapshotLength; i++) {
                const e = allMatchElements.snapshotItem(i)
                if (e !== element) {
                    this.#preSelectElements.set(e, [e.style.backgroundColor, e.style.border])
                    this.#preSelect(e)
                }
            }
            // 没有预选到同类元素
            if (this.#preSelectElements.size === 0) {
                this.#selectedXpathMatchSimilar = null
            }
        } else {
            if (this.#allowPreSelect) {
                this.#selectedXpathMatchSimilar = null
            }
            this.#allowPreSelect = false
            this.#getXpathMatchUnique(element)
            this.#selectedElements.set(element, [this.#overElementOriginBackgroundColor, element.style.border])
            this.#select(element)
        }
        this.#overElementOriginBackgroundColor = 'rgba(13,142,4,0.3)'
    }

    // 设置为被预选中的样式
    #preSelect(element) {
        element.style.backgroundColor = 'rgba(255,0,77,0.3)';
        element.style.border = \`3px dashed red\`
    }

    // 设置为被选中的样式
    #select(element) {
        element.style.backgroundColor = 'rgba(13,142,4,0.3)';
        element.style.border = \`3px solid green\`
    }

    // 设置为原始样式
    #cancelSelect(element, styles) {
        element.style.backgroundColor = styles[0] || ''
        element.style.border = styles[1] || ''
    }

    // 将所有预选中元素转换成选中状态
    preSelect2Selected() {
        this.#preSelectElements.forEach((v, k, m) => {
            this.#selectedElements.set(k, v)
            k.style.backgroundColor = 'rgba(13,142,4,0.3)';
            k.style.border = \`3px solid green\`
        })
        this.#allowPreSelect = false
        // 保留匹配多个相似元素的xpath，删除用户选择的那一个元素的xpath
        this.#selectedXpathMatchUnique.splice(0, 1)
        this.#preSelectElements.clear()
    }

    clearPreSelect() {
        this.#preSelectElements.forEach((v, k, m) => {
            this.#cancelSelect(k, v)
        })
    }

    reset() {
        this.#preSelectElements.forEach((v, k, m) => {
            this.#cancelSelect(k, v)
        })
        this.#selectedElements.forEach((v, k, m) => {
            this.#cancelSelect(k, v)
        })
        this.#clearXpathTestSelect()
        this.#overElement = null
        this.#overElementOriginBackgroundColor = null
        this.#selectedElements = new Map()
        this.#preSelectElements = new Map()
        this.#selectedXpathMatchUnique = []
        this.#selectedXpathMatchSimilar = null
        this.#xpathTemplate = []
        this.#allowPreSelect = true
        this.#testXpathSelectElements = new Map()
        this.#selectScene = 0
    }

    getAllowPreSelect() {
        return this.#allowPreSelect
    }

    setAllowPreSelect(allowPreSelect) {
        this.#allowPreSelect = allowPreSelect
    }

    getSelectedElements() {
        return this.#selectedElements
    }

    getPreSelectElements() {
        return this.#preSelectElements
    }

    // 获取所有选中元素的xpath
    getSelectedXpath() {
        if (this.#preSelectElements.size > 0) {
            return this.#selectedXpathMatchUnique
        }
        if (this.#selectedXpathMatchSimilar === null) {
            return this.#selectedXpathMatchUnique
        }
        return [this.#selectedXpathMatchSimilar].concat(this.#selectedXpathMatchUnique)
    }

    xpathTest(xpathList) {
        this.#clearXpathTestSelect()
        for (let xpath of xpathList) {
            const eles = document.evaluate(xpath, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null)
            for (let i = 0; i < eles.snapshotLength; i++) {
                const ele = eles.snapshotItem(i)
                // 记录选中的元素
                this.#testXpathSelectElements.set(ele, ele.style.border)
                // 设置边框表示测试选中
                ele.style.border = \`3px dashed rgba(0,73,255,0.3)\`
            }
        }
    }

    #clearXpathTestSelect() {
        this.#testXpathSelectElements.forEach((v, k, m) => {
            k.style.border = v
        })
        this.#testXpathSelectElements.clear()
    }

    extractData(xpathList, data, condition = null) {
        const dataList = []
        for (let xpath of xpathList) {
            const eles = document.evaluate(xpath, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null)
            for (let i = 0; i < eles.snapshotLength; i++) {
                const ele = eles.snapshotItem(i)
                // 该数据提取节点在一个条件分支内，对于每一个要提取数据的元素项都要先检验其是否满足条件
                if (condition != null) {
                    switch (condition.type) {
                        case 0:
                            const regexp = new RegExp(condition.pattern)
                            if (!regexp.test(ele.innerText)) {
                                continue
                            }
                            break
                    }
                }
                const item = []
                for (let col of data) {
                    switch (col.type) {
                        case 0:
                            item.push(ele.innerText)
                            break
                        case 1:
                            let e = ele.parentElement.querySelector('a')
                            if (e != null) {
                                item.push(e.href)
                            } else {
                                e = ele.parentElement.querySelector('img')
                                if (e != null) {
                                    item.push(e.src)
                                }
                            }
                            break
                        default:
                            item.push(getExtraData(col.type))
                            break
                    }
                }
                dataList.push(item)
            }
        }
        console.log('dataType', dataList)
        return dataList
    }

    setSelectScene(scene) {
        this.#selectScene = scene
    }

    getSelectScene() {
        return this.#selectScene
    }
}

const elementSelector = new ElementSelector()

document.addEventListener('mouseover', (event) => elementSelector.mouseOver(event))
document.addEventListener('mouseout', (event) => elementSelector.mouseOut(event))
document.addEventListener('contextmenu', (event) => {
    elementSelector.mouseSelect(event)
    if (elementSelector.getSelectScene() === 1) {
        elementSelector.setSelectScene(0)
        return
    }
    console.log(elementSelector)

    // 每次右击选中与元素后，向操作渲染进程汇报最新的选中状态
    window.embeddedPageEvent.selectStatus(elementSelector.getAllowPreSelect(),
        elementSelector.getSelectedElements().size, elementSelector.getPreSelectElements().size, elementSelector.getSelectedXpath())
})

window.embeddedPageEvent.onCancelSelect((event) => {
    elementSelector.reset()
})

window.embeddedPageEvent.onSelectAll((event) => {
    elementSelector.preSelect2Selected()
    // 告知操作渲染进程最新的选择状况
    console.log(elementSelector.getSelectedXpath())
    window.embeddedPageEvent.selectStatus(elementSelector.getAllowPreSelect(),
        elementSelector.getSelectedElements().size, elementSelector.getPreSelectElements().size, elementSelector.getSelectedXpath())
})

window.embeddedPageEvent.onTestXpath((event, xpathList) => {
    elementSelector.reset()
    elementSelector.xpathTest(xpathList)
})

window.embeddedPageEvent.onPreviewData((event, xpathList, data, condition = null) => {
    const dataList = elementSelector.extractData(xpathList, data, condition)
    window.embeddedPageEvent.returnExtractData(dataList)
})

window.embeddedPageEvent.onSelectScene((event, selectScene) => {
    elementSelector.setSelectScene(selectScene)
})

window.embeddedPageEvent.onNeedExtraData((event, type) => {
    window.embeddedPageEvent.returnExtraData(type, getExtraData(type))
})

// 获取一些额外信息，主要是页面元信息
function getExtraData(type) {
    switch (type) {
        case 50:
            return window.location.href
        case 51:
            const titles = []
            document.querySelectorAll('title').forEach((e) => {
                titles.push(e.innerText)
            })
            return titles.join('')
        case 52:
            return currentDateTime()
    }
}


function currentDateTime() {
    const date = new Date();
    const year = date.getFullYear();
    const month = (date.getMonth() + 1).toString().padStart(2, '0');
    const day = date.getDate().toString().padStart(2, '0');
    const hour = date.getHours().toString().padStart(2, '0');
    const minute = date.getMinutes().toString().padStart(2, '0');
    const second = date.getSeconds().toString().padStart(2, '0');
    return \`\${year}-\${month}-\${day} \${hour}:\${minute}:\${second}\`;
}
    `)
});

$('#exitBtn').on('click', function () {
    // 页面切换至 home
    window.pageEvent.switchPage(0)
});

// 用户在导航栏输入URL后回车，webview重新加载内容
$('#urlInput').keydown((event) => {
    if (event.key === "Enter") {
        wv.loadURL($('#urlInput').val())
    }
})

$('#reloadBtn').click(() => {
    wv.reload()
})
$('#backBtn').click(() => {
    wv.goBack()
})
$('#forwardBtn').click(() => {
    wv.goForward()
})

// 监听来自 webview 的消息
wv.addEventListener('ipc-message', (event) => {
    switch (event.channel) {
        case 'returnExtractData':
            onReturnExtractData(event.args[0])
            break
        case 'selectStatus':
            onSelectStatus(event.args[0], event.args[1], event.args[2], event.args[3])
            break
        case 'returnExtraData':
            addExtraData(event.args[0], event.args[1])
            break
        case 'returnSelectXpath':
            onSelectXpath(event.args[0])
            break
    }
});


const taskFlowGenerator = new TaskFlowGenerator()

// 流程图中此时被选中的元素
let selectedNode = null

let insertNodePos = null

// 内容渲染进程中，关于选中元素的一些信息
let allowPreSelectStatus = true
let selectedNum = 0
let selectedXpathList = []
let preSelectNum = 0

// 数据预览表格
let dataPreviewTable = null
// 记录从内嵌网页传来的原始数据
let originData

// 点击表格头时，选中的是第几列
let selectedCol = -1
// 数据预览对应的数据提取节点
let dataPreviewNode = null


// 在编辑后处理步骤时，记录当前项在ul中的位置
let selectReProcessStep = null
// 暂存数据后处理步骤
let reprocessStepList = []

// 任务详情
let task
let allCategories

const urlPrefix = "http://localhost:8080/"

// 此时拖动的任务流节点
let draggedNode = null
// 拖动的任务流节点是否已经被选中，如果是，则在拖动完成后需要更新selectedNode
let isDragSelectNode = false

// 刚跳转到crawler_detail页面时，获知爬虫的根url列表。做两件事：1.加载初始页面 2.绘制初始流程图
window.pageEvent.onRootUrl((event, param) => {
    task = param.task
    allCategories = param.allCategories

    // 让webview加载初始的url页面
    wv.src = task.urlList[0]
    // 更改导航栏的值
    $('#urlInput').val(task.urlList[0])

    // 如果参数包含任务定义，则绘制完整流程图
    if (task.taskDefine !== "") {
        buildTaskFlow(JSON.parse(task.taskDefine).nodes)
    } else if (task.urlList.length > 1) {
        taskFlowGenerator.addNode(new LoopNode(loopTypeEnum.LOOP_URL, taskFlowGenerator.getNextNodeAddPos().join('-'),
            null, `循环${taskFlowGenerator.getNodeCount(nodeTypeEnum.LOOP) + 1}`, -1, task.urlList))
        taskFlowGenerator.addNode(new OpenPageNode(taskFlowGenerator.getNextNodeAddPos().join('-'),
            `打开网页${taskFlowGenerator.getNodeCount(nodeTypeEnum.OPEN_PAGE) + 1}`))
    } else {
        taskFlowGenerator.addNode(new OpenPageNode(taskFlowGenerator.getNextNodeAddPos().join('-'),
            `打开网页${taskFlowGenerator.getNodeCount(nodeTypeEnum.OPEN_PAGE) + 1}`, task.urlList[0]))
    }
})

// 获知webview中的元素选择情况，并更新操作面板上上侧的文本
function onSelectStatus(allowPreSelect, selected, preSelect, selectedXpath) {
    allowPreSelectStatus = allowPreSelect
    selectedNum = selected
    preSelectNum = preSelect
    selectedXpathList = selectedXpath
    if (allowPreSelect) {
        $('#select-status').text(`您已选中${selected}个元素，系统检测到有${preSelect + selected}个相似元素。您可以进行以下操作：`)
    } else {
        $('#select-status').text(`您已选中${selected}个元素。您可以进行以下操作：`)
    }
}

// ----------------------------------- 操作面板操作 -----------------------------------
$('#cancel-select').click(cancelSelect)

$('#select-all').click(() => {
    wv.send('selectAll')
})


$('#extract-text').click(() => {
    extractData(dataTypeEnum.TEXT)
})

$('#extract-link').click(() => {
    extractData(dataTypeEnum.URL)
})

$('#extract-link-text').click(() => {
    extractData(dataTypeEnum.TEXT_URL)
})

// 点击一个元素
$('#click-element').click(() => {
    taskFlowGenerator.addNode(new ClickElementNode(taskFlowGenerator.getNextNodeAddPos().join('-'),
        `点击元素${taskFlowGenerator.getNodeCount(nodeTypeEnum.CLICK_ELEMENT) + 1}`, selectedXpathList[0]))
    cancelSelect()
})

// 循环点击一个元素
$('#click-element-loop').click(() => {
    // 插入循环
    taskFlowGenerator.addNode(new LoopNode(loopTypeEnum.LOOP_PAGING, taskFlowGenerator.getNextNodeAddPos().join('-'),
        selectedXpathList, `循环${taskFlowGenerator.getNodeCount(nodeTypeEnum.LOOP) + 1}`))
    const lastPos = objectCopy(taskFlowGenerator.getNextNodeAddPos())
    console.log('lastPos', lastPos)
    // 循环中插入点击
    taskFlowGenerator.addNode(new ClickElementNode(taskFlowGenerator.getNextNodeAddPos().join('-'), '点击翻页', null, true))
    taskFlowGenerator.setNextNodeAddPos(lastPos)

    cancelSelect()
})

// 循环点击元素列表
$('#click-each-element-loop').click(() => {
    // 插入循环
    taskFlowGenerator.addNode(new LoopNode(loopTypeEnum.LOOP_LIST, taskFlowGenerator.getNextNodeAddPos().join('-'),
        selectedXpathList, `循环${taskFlowGenerator.getNodeCount(nodeTypeEnum.LOOP) + 1}`))
    // 循环中插入点击
    taskFlowGenerator.addNode(new ClickElementNode(taskFlowGenerator.getNextNodeAddPos().join('-'), '点击元素'))
})
// ---------------------------------------------------------------------------------------

// 用户在流程图上点击箭头插入节点
$('#add-node-menu li').click((event) => {
    const element = event.target

    console.log(element.name)
    const nodeType = parseInt(element.id)
    // 进行节点插入
    const insertNodePosArr = insertNodePos.split('-')
    for (let i = 0; i < insertNodePosArr.length; i++) {
        insertNodePosArr[i] = parseInt(insertNodePosArr[i])
    }
    insertNodePosArr[insertNodePosArr.length - 1]++
    taskFlowGenerator.setNextNodeAddPos(objectCopy(insertNodePosArr))
    taskFlowGenerator.addNode(buildEmptyNode(nodeType, insertNodePosArr.join('-')))
})


$('#cancel').click((event) => {
    hideNodeConfig()

    $('#node-config-content > div.optional').hide()
    taskFlowGenerator.cancelSelectNode(selectedNode)
})

$('#task-flow').on('click', (event) => {
    if (event.target.id === 'task-flow' && selectedNode != null) {
        hideNodeConfig()

        $('#node-config-content > div.optional').hide()
        taskFlowGenerator.cancelSelectNode(selectedNode)
    }
})

$('#delete').click((event) => {
    console.log('delete node')
    let element = selectedNode
    if (element.className.includes('t2')) {
        element = element.parentElement
    }
    // 删除对应节点
    taskFlowGenerator.deleteNode(element.id)
    element.nextElementSibling.remove()
    element.remove()
    // 隐藏节点消息面板
    hideNodeConfig()
    $('#node-config-content > div.optional').hide()
    taskFlowGenerator.cancelSelectNode(selectedNode)
})

// 更新节点属性
$('#apply').click((event) => {
    console.log(selectedNode)
    let node
    if (selectedNode.className.includes('t2-1')) {
        node = taskFlowGenerator.getNode(selectedNode.parentElement.id)
    } else {
        node = taskFlowGenerator.getNode(selectedNode.id)
    }
    // -------------------------更新通用部分
    // 更新节点名称
    node.setName($('#ipt-step-name').val())
    // 更新‘执行前等待’
    node.setBeforeExec(parseInt($('#before-wait').val()))
    // 更新是否随机等待
    node.setBeforeExecIsRandom($('#random-wait').prop('checked'))

    // -------------------------更新其他部分
    switch (node.getType()) {
        case nodeTypeEnum.OPEN_PAGE:
            console.log("!@#$", $('#timeout').val())
            node.setTimeout(parseInt($('#timeout').val()))
            node.setRetryTimes(parseInt($('#retry-times').val()))
            node.setRetryInterval(parseInt($('#retry-interval').val()))
            const iptUrl = $('#ipt-page-url')
            if (iptUrl.prop('readonly')) {
                node.setUrl(null)
            } else {
                node.setUrl(iptUrl.val())
                // 如果是第一个OpenPage节点，则需要更新task的urlList字段
                if (node.getId() === '2') {
                    task.urlList = [iptUrl.val()]
                }
            }
            break
        case nodeTypeEnum.EXTRACT_DATA:
            const newDataType = parseInt($('#selector-dataType').val())
            if (newDataType !== node.getDataType()) {
                switch (newDataType) {
                    case dataTypeEnum.TEXT_URL:
                        node.setData([{name: "字段1", type: dataTypeEnum.TEXT, reprocess: []}, {
                            name: "字段2",
                            type: dataTypeEnum.URL,
                            reprocess: []
                        }])
                        break
                    case dataTypeEnum.TEXT:
                    case dataTypeEnum.URL:
                        node.setData([{name: "字段1", type: newDataType, reprocess: []}])
                        break
                    case dataTypeEnum.NONE:
                        node.setData([])
                        break
                }
                node.setDataType(newDataType)
            }
        // 继续执行下一个case
        case nodeTypeEnum.CLICK_ELEMENT:
            const iptXpath = $('#ipt-xpath')
            if (iptXpath.prop('readonly')) {
                node.setXpath(null)
            } else {
                node.setXpath(iptXpath.val())
            }
            break
        case nodeTypeEnum.LOOP:
            if ($('#ck-use-loop-times').prop('checked')) {
                node.setLoopTimes($('#ipt-loop-times').val())
            } else {
                node.setLoopTimes(-1)
            }
            node.setLoopType(parseInt($('#selector-loopType').val()))
            console.log('loop type', parseInt($('#selector-loopType').val()))
            switch (node.getLoopType()) {
                case loopTypeEnum.LOOP_URL:
                    node.setUrls($('#ta-url').val().split('\n'))
                    node.setXpathList(null)
                    // 如果是第一个循环网址，则需要更新task的urlList字段
                    if (node.getId() === '2') {
                        task.urlList = objectCopy(node.getUrls())
                    }
                    break
                case loopTypeEnum.LOOP_PAGING:
                    node.setXpathList($('#ipt-xpath').val().split('\n'))
                    node.setUrls(null)
                    break
                case loopTypeEnum.LOOP_LIST:
                    node.setXpathList($('#ta-xpath').val().split('\n'))
                    node.setUrls(null)
                    break
            }
            if ($('#ck-use-loop-times').prop('checked')) {
                node.setLoopTimes(parseInt($('#ipt-loop-times').val()))
            }
            break
        case nodeTypeEnum.BRANCH:
            node.setPattern($('#condition-pattern').val())
            node.setConditionType(parseInt($('#selector-conditionType').val()))
            break
    }
    if (node.getType() === nodeTypeEnum.EXTRACT_DATA) {
        if (node.getDataType() !== dataTypeEnum.NONE) {
            let condition = null
            let parentNode = null
            if (node.getId().includes('-')) {
                parentNode = taskFlowGenerator.getNode(selectedNode.parentElement.id)
                if (parentNode.getType() === nodeTypeEnum.BRANCH) {
                    condition = {
                        type: parentNode.getConditionType(),
                        pattern: parentNode.getPattern()
                    }
                }
            }
            // 处于循环中的数据提取节点
            if (node.getXpath() === null) {
                if (condition == null) {
                    wv.send('previewData', parentNode.getXpathList(), node.getData(), condition)
                } else {
                    wv.send('previewData', taskFlowGenerator.getNode(selectedNode.parentElement.parentElement.parentElement.id).getXpathList(),
                        node.getData(), condition)
                }
            } else {
                wv.send('previewData', [node.getXpath()], node.getData(), condition)
            }
            dataPreviewNode = selectedNode
        } else {
            $('#data-display-tb').hide()
            $('#no-data').show()
        }
    }

    hideNodeConfig()
    $('#node-config-content > div.optional').hide()
    taskFlowGenerator.cancelSelectNode(selectedNode)

    console.log(node)
})

function onReturnExtractData(dataList) {
    const colNum = dataList[0].length
    const rowNum = dataList.length
    const cols = []
    // 构造表头
    cols.push({field: '-1', title: '序号', width: 80, fixed: 'left'})
    const dt = taskFlowGenerator.getNode(dataPreviewNode.id).getData()
    for (let i = 0; i < colNum; i++) {
        cols.push({field: i + '', title: dt[i].name})
    }

    const data = []
    // 构造数据
    for (let i = 0; i < rowNum; i++) {
        const row = {'-1': i + ''}
        for (let j = 0; j < colNum; j++) {
            row[j + ''] = dataList[i][j]
            row[j + ''] = reprocessData(row[j + ''], dt[j].reprocess)
        }
        data.push(row)
    }

    // 渲染表格
    dataPreviewTable = renderTable(cols, data)
    // 记录原始数据
    originData = objectCopy(data)

    // 显示表格
    $('#no-data').hide()
    $('#reprocess-operate-background').hide()
    $('#data-display-tb').show()
}

function onSelectXpath(xpath) {
    // 用户已经通过鼠标点击元素，完成了对节点xpath属性的修改
    $('#ipt-xpath').val(xpath)

    const ele = $('#select-ele')
    ele.css('background-color', '')
    ele.data('isSelect', 'false')
}

$('#select-ele').click((event) => {
    if ($('#ipt-xpath').prop('readonly')) {
        return
    }

    const ele = $(event.target)
    if (ele.data('isSelect') !== 'true') {
        ele.css('background-color', '#ececec')
        ele.data('isSelect', 'true')
        // 告知内容渲染进程用户即将选择一个元素并获取xpath。对应的场景是用户要修改流程图节点的xpath属性
        wv.send('selectScene', 1)
    } else {
        ele.css('background-color', '')
        ele.data('isSelect', 'false')
    }
})

$('#add-col').click((event) => {
    if ($('#data-display-tb').css('display') !== 'none') {
        showCustomMenu(event, 'add-col-menu')
    }
})

$('#clear-data').click(() => {
    if ($('#data-display-tb').css('display') !== 'none') {
        $('#data-display-tb').hide()
        $('#no-data').show()
        dataPreviewNode = null
    }
})

$('#data-display-tb').on('click', '#col-operation', (event) => {
    selectedCol = parseInt($(event.target).parent().parent().attr('data-field'))
    // 右击序号列（第一列）不弹出菜单
    if (selectedCol === -1) {
        return
    }
    showCustomMenu(event, 'col-reprocess')
})

$('#add-col-menu li').click((event) => {
    if (!dataPreviewNode.className.includes('extract-data')) {
        return
    }

    wv.send('needExtraData', parseInt(event.target.id))
})

$('#col-reprocess li').click((event) => {
    if (!dataPreviewNode.className.includes('extract-data')) {
        return
    }

    if (event.target.id === 'reprocess') {
        showDataReprocessBoard()
    } else {
        deleteDataCol()
    }
})


$("#add-step").click(function (event) {
    showCustomMenu(event, 'step-list')
});

// 添加新的后处理步骤
$("#step-list").on("click", "li", function (event) {
    const type = event.target.id
    const newItem = $(`<li id="${type}"></li>`)
        .addClass("list-item")
        .html(`${$(this).text()}<span class="delete-icon">×</span>`)
        .attr("data-action", $(this).attr("data-action"));
    $("#list").append(newItem);
    reprocessStepList.push({type: parseInt(type), param: ''})

    $("#step-list").hide();

    // 显示当前节点的配置面板
    switch (type) {
        case reprocessEnum.REG_EXP:
            $('#ipt-regexp').val('')
            $('#origin-text').val('')
            $('#match-text').val('')
            break
        case reprocessEnum.ADD_PREFIX:
            $('#ipt-prefix').val('')
            break
        case reprocessEnum.ADD_SUFFIX:
            $('#ipt-suffix').val('')
            break
    }
    // 显示配置
    $('.opt').hide()
    $(`.p${type}`).show()
    selectReProcessStep = $("#list li").length - 1
});

// 编辑某一个后处理步骤
$('#list').on('click', '.list-item', (event) => {
    let ele = event.target
    if (ele.tagName === 'SPAN') {
        return
    }
    $('#right-div div').hide()
    // 获取该step在其ul中的索引位置
    const itemNo = $(ele).index()
    // 渲染该后处理步骤对应的配置
    const type = parseInt(ele.id)
    const param = reprocessStepList[itemNo].param
    switch (type) {
        case reprocessEnum.REG_EXP:
            $('#ipt-regexp').val(param)
            $('#origin-text').val('')
            $('#match-text').val('')
            break
        case reprocessEnum.ADD_PREFIX:
            $('#ipt-prefix').val(param)
            break
        case reprocessEnum.ADD_SUFFIX:
            $('#ipt-suffix').val(param)
            break
    }
    // 显示配置
    $(`.p${type}`).show()
    selectReProcessStep = itemNo
})

$('#list').on('click', '.delete-icon', (event) => {
    $(`.p${event.target.parentElement.id}`).hide()
    const li = $(event.target.parentElement)
    // 删除该step
    reprocessStepList.splice(li.index(), 1)
    li.remove()

})

$('#test-regexp').click(() => {
    const regexp = new RegExp($('#ipt-regexp').val(), 'g');
    const matchResult = $('#origin-text').val().match(regexp)
    if (matchResult != null) {
        $('#match-text').val(matchResult.join(''))
    } else {
        $('#match-text').val('空')
    }
})

// 确认某一个后处理步骤的配置
$('#yes').click((event) => {
    const type = parseInt($(`#list li:eq(${selectReProcessStep})`).attr('id'))
    switch (type) {
        case reprocessEnum.REG_EXP:
            reprocessStepList[selectReProcessStep].param = $('#ipt-regexp').val()
            break
        case reprocessEnum.ADD_PREFIX:
            reprocessStepList[selectReProcessStep].param = $('#ipt-prefix').val()
            break
        case reprocessEnum.ADD_SUFFIX:
            reprocessStepList[selectReProcessStep].param = $('#ipt-suffix').val()
            break
    }
    $(`.p${type}`).hide()

})

$('#no').click((event) => {
    const type = parseInt($(`#list li:eq(${selectReProcessStep})`).attr('id'))
    $(`.p${type}`).hide()
})

$('#apply-reprocess').click(() => {
    // 将暂存的后处理步骤更新到ExtractDataNode节点
    const dataList = taskFlowGenerator.getNode(dataPreviewNode.id).getData()
    dataList[selectedCol].reprocess = reprocessStepList
    reprocessStepList = null

    $('#reprocess-operate-background').hide()
    $('#data-display-tb').show()
    $('.opt').hide()

    // 重新渲染表格，显示后处理的效果
    const reprocess = dataList[selectedCol].reprocess
    const data = layui.table.cache["ID-table-demo-data"]
    for (let i = 0; i < data.length; i++) {
        data[i]["" + selectedCol] = reprocessData(originData[i]["" + selectedCol], reprocess)
    }
    layui.table.renderData("ID-table-demo-data")
})

// 后处理原始数据
function reprocessData(originText, reprocess) {
    let prefix = ""
    let suffix = ""
    for (let j = 0; j < reprocess.length; j++) {
        switch (reprocess[j].type) {
            case reprocessEnum.CLEAR_EMPTY:
                originText = originText.trim()
                break
            case reprocessEnum.ADD_PREFIX:
                prefix = reprocess[j].param
                break
            case reprocessEnum.ADD_SUFFIX:
                suffix = reprocess[j].param
                break
            case reprocessEnum.REG_EXP:
                const regexp = new RegExp(reprocess[j].param, 'g');
                const matchResult = originText.match(regexp)
                if (matchResult != null) {
                    originText = matchResult.join('')
                } else {
                    originText = ''
                }
                break
        }
    }
    return prefix + originText + suffix
}

$('#cancel-reprocess').click(() => {
    $('#reprocess-operate-background').hide()
    $('#data-display-tb').show()
    $('.opt').hide()
})

// 保存任务（仅保存任务定义字段）
$('#saveBtn').click(() => {
    $.ajax({
        url: urlPrefix + "task/updateTaskDefine" + `?taskId=${task.taskId}`,
        type: 'PUT',
        contentType: 'application/plain',
        data: taskFlowGenerator.getTask(),
        success: function (response) {
            if (response.code !== "200") {
                layer.msg("任务定义保存失败：" + response.msg)
                return
            }
            layer.msg("任务定义保存成功")
        },
        error: function (xhr, status, error) {
            layer.msg(`任务定义保存失败，status:${status}, error:${error}`)
        }
    })
})

$('#btn-test-xpath').click(() => {
    const xpath = $('#ipt-xpath').val();
    // 将xpath经由主进程发送给内容渲染进程
    wv.send('testXpath', [xpath])
})

$('#btn-test-xpath-list').click(() => {
    const xpathList = $('#ta-xpath').val().split('\n')
    wv.send('testXpath', xpathList)
    console.log('send xpathTest')
})

// 查看任务详情页
$('#detailBtn').click(() => {
    task.taskDefine = taskFlowGenerator.getTask()
    window.pageEvent.switchPage(3, {
        task: task,
        allCategories: allCategories
    })
})

// 编辑数据预览表头表头
$('#data-display-tb').on('dblclick', 'th', function (event) {
    let ele = event.target;
    if (ele.tagName === 'TH') {
        ele = ele.firstChild;
    } else if (ele.tagName === 'SPAN' || ele.tagName === 'IMG') {
        ele = ele.parentElement;
    }

    // 序号列不允许修改表头
    const col = parseInt(ele.parentElement.dataset.field)
    if (col === -1) {
        return
    }

    const span = ele.firstChild
    const oldText = span.innerText
    const html = ele.innerHTML
    ele.innerHTML = `<input type="text" id="ipt-tmp" value="${oldText}">`
    ele.firstChild.addEventListener('keydown', function (e) {
        if (e.key === 'Enter') {
            const newText = $('#ipt-tmp').val()
            ele.innerHTML = html
            ele.firstChild.innerHTML = newText
            // 修改ExtractDataNode的，data.name字段
            const data = taskFlowGenerator.getNode(dataPreviewNode.id).getData()
            data[col].name = newText

            // 修改table的表头
            const cols = dataPreviewTable.config.cols[0]
            cols[col + 1].title = newText
        }
    })
})

// 流程图节点拖动相关逻辑
$('#task-flow').on('dragstart', '.t1,.t2-1', function (event) {
    draggedNode = $(this)
    draggedNode.css('opacity', '0.5')
    if (selectedNode === event.target) {
        isDragSelectNode = true
    }
})
$('#task-flow').on('dragend', '.t1,.t2-1', function (event) {
    // 阻止继续冒泡
    draggedNode.css('opacity', '1')
    draggedNode = null
})
// 拖动元素时经过箭头节点，改变其样式表示可以放置
$('#task-flow').on('dragover', '.arrow', function (event) {
    event.preventDefault()
    event.target.src = '../../resources/node-add.svg';
})
// 恢复箭头节点样式
$('#task-flow').on('dragleave', '.arrow', function (event) {
    event.target.src = '../../resources/arrow-down.svg';
})
$('#task-flow').on("drop", '.arrow', function (event) {
    event.target.src = '../../resources/arrow-down.svg';
    if (draggedNode == null) {
        return
    }
    draggedNode.css('opacity', '1')

    const $currArrowEle = $(event.target)
    // 如果拖动元素后又放回了原位置，则不做处理
    let dId
    if (draggedNode.hasClass('t2-1')) {
        dId = draggedNode.parent().attr('id')

    } else {
        dId = draggedNode.attr('id')
    }
    if (dId === $currArrowEle.next().attr('id') || dId === $currArrowEle.prev().attr('id')) {
        return
    }

    let $oldNodeEle
    if (draggedNode.hasClass('t2-1')) {
        $oldNodeEle = draggedNode.parent()
    } else {
        $oldNodeEle = draggedNode
    }

    const oldNodeId = $oldNodeEle.attr('id')
    const oldNode = taskFlowGenerator.getNode(oldNodeId)

    // 从旧位置删除节点
    taskFlowGenerator.deleteNode(oldNodeId)
    const $oldNodeEleArrow = $oldNodeEle.next()
    $oldNodeEleArrow.remove()
    $oldNodeEle.remove()
    // 计算新Id
    let newIdArr = $currArrowEle.attr('id').split('-').map(Number)
    newIdArr[newIdArr.length - 1]++
    oldNode.setId(newIdArr.join('-'))
    $oldNodeEle.attr('id', newIdArr.join('-'))
    // 向新位置插入节点
    newIdArr[newIdArr.length - 1]++
    $oldNodeEleArrow.attr('id', newIdArr.join('-'))
    newIdArr[newIdArr.length - 1]--
    oldNode.setHtml($oldNodeEle.prop('outerHTML') + $oldNodeEleArrow.prop('outerHTML'))
    taskFlowGenerator.setNextNodeAddPos(objectCopy(newIdArr))
    taskFlowGenerator.addNode(oldNode)
    $oldNodeEleArrow.attr('id', newIdArr.join('-'))
    // 修正移动的节点的内部嵌套结构的节点ID

    fixIdForDragNode(oldNode)
    console.log(taskFlowGenerator)

    if (isDragSelectNode) {
        selectedNode = document.getElementById(oldNode.getId())
        if (draggedNode.hasClass('t2-1')) {
            selectedNode = selectedNode.querySelector(`.t2-1`)
        }
    }
})

function fixIdForDragNode(node) {
    console.log(node)
    switch (node.getType()) {
        case nodeTypeEnum.LOOP:
        case nodeTypeEnum.CONDITION:
        case nodeTypeEnum.BRANCH:
            const children = node.getSubNodes()
            for (let i = 0; i < children.length; i++) {
                const newId = `${node.getId()}-${i}`
                $(`#${children[i].getId()}`).attr('id', newId)
                children[i].setId(newId)
                fixIdForDragNode(children[i])
            }
            break
        default:
            return
    }
}

// 执行任务
$('#captureBtn').on('click', function (event) {
    task.taskDefine = taskFlowGenerator.getTask()
    // 进行一次任务定义保存
    $.ajax({
        url: urlPrefix + "task/updateTaskDefine" + `?taskId=${task.taskId}`,
        type: 'PUT',
        contentType: 'application/plain',
        data: task.taskDefine,
        success: function (response) {
            if (response.code !== "200") {
                layer.msg("任务定义保存失败：" + response.msg)
                return
            }
            layer.msg("任务定义保存成功")
            // 切换页面到执行页面
            console.log(task)
            window.pageEvent.switchPage(4, {
                task: task,
                allCategories: allCategories
            })
        },
        error: function (xhr, status, error) {
            layer.msg(`任务定义保存失败，status:${status}, error:${error}`)
        }
    })
})